上篇揭開MVC常用的過濾器如何被獲取呼叫跟基本介紹.
前幾篇有介紹ControllerDescriptor
,ActionDescriptor
兩個物件,今天會來細部探討他們裡面有哪些重要成員.
本篇會繼續分析呼叫Action
方法邏輯和在過程中有用到重要物件跟動作
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
前面有說ControllerActionInvoker
類別最重要的就是InvokeAction
方法,因為主要透過他去呼叫ActionResult
抽象類別ExecuteResult
方法.
InvokeAction
有兩個參數
ControllerContext
:對於RequestContext
,RouteData
,使用Controller
資訊封裝.actionName
:此次呼叫方法(從RouteData
取得action
值)public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
InvokeAction
方法除了呼叫ExecuteResult
方法外還做了其他事情,對於藉由ControllerContext
封裝兩個物件.
ControllerDescriptor
ActionDescriptor
這兩個物件在此方法中很重要.
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
GetControllerDescriptor
會返回一個ReflectedControllerDescriptor
物件.
protected virtual ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext)
{
Type controllerType = controllerContext.Controller.GetType();
ControllerDescriptor controllerDescriptor = DescriptorCache.GetDescriptor(
controllerType: controllerType,
creator: (Type innerType) => new ReflectedControllerDescriptor(innerType),
state: controllerType);
return controllerDescriptor;
}
ReflectedControllerDescriptor
裡面有許多重要資訊,我會列出其重要成員和代表含意.
ControllerType
此次執行Controller
類型FindAction
透過此方法取得ActionDescriptor
物件.GetFilterAttributes
方法會透過此物件取得AcitonFilter
(掛載在Controller
上)public abstract class ControllerDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{
public virtual string ControllerName
{
get
{
string typeName = ControllerType.Name;
if (typeName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
{
return typeName.Substring(0, typeName.Length - "Controller".Length);
}
return typeName;
}
}
public abstract Type ControllerType { get; }
public abstract ActionDescriptor FindAction(ControllerContext controllerContext, string actionName);
public abstract ActionDescriptor[] GetCanonicalActions();
public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
{
return GetCustomAttributes(typeof(FilterAttribute), inherit: true).Cast<FilterAttribute>();
}
public virtual bool IsDefined(Type attributeType, bool inherit)
{
if (attributeType == null)
{
throw new ArgumentNullException("attributeType");
}
return false;
}
}
ReflectedControllerDescriptor
實現FindAction
抽象方法.
主要透過反射取得此Controller
物件中相對應Action名稱的方法,並把他封裝到ReflectedActionDescriptor
類別中返回.
public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(actionName))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}
MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName);
if (matched == null)
{
return null;
}
return new ReflectedActionDescriptor(matched, actionName, this);
}
執行每一個Action
方法會通過ActionDescriptor
物件,所以ActionDescriptor
是另一個對於InovkeAction
方法來說很重要物件
在ActionDescriptor
抽象類別中有許多重要的成員
Execute
:Action
執行呼叫方法,其中裡面的parameters
參數就是調用Controller
上Action
方法鎖使用的參數.GetFilterAttributes
:回傳在Action
方法上的所有Filter
標籤GetFilters
:返回一個FilterInfo
物件,這個物件可以得到應用在該Action
方法上所有filter
ActionName
:Action
方法名稱public abstract class ActionDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{
//....
public virtual bool IsDefined(Type attributeType, bool inherit);
public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache);
public abstract ParameterDescriptor[] GetParameters();
public abstract object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);
public virtual FilterInfo GetFilters();
public abstract string ActionName { get; }
public abstract ControllerDescriptor ControllerDescriptor { get; }
public virtual string UniqueId { get; }
}
繼承這個抽象類的子類就會擁有一種特性描述此次執行Action
方法特徵和如何去使用Execute
方法.
上面提到ReflectedControllerDescriptor
的ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
預設使用ReflectedActionDescriptor
.
ReflectedActionDescriptor
類別顧名思義就是依靠反射來取得Action
的資訊
切入重點我們來看看ReflectedActionDescriptor
如何實現Execute
方法的吧
MethodInfo
是從ReflectedControllerDescriptor
利用反射取得執行Action
方法資訊.ExtractParameterFromDictionary
方法將IDictionary<string, object> parameters
傳入參數轉成可傳入方法物件.ActionMethodDispatcher
物件Execute
方法執行Action
方法(ActionMethodDispatcher
透過Expression
表達式動態建立方法並呼叫)
ActionMethodDispatcher
的Expression
表達式詳解會在後面做介紹
public MethodInfo MethodInfo { get; private set; }
public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
{
//....
ParameterInfo[] parameterInfos = MethodInfo.GetParameters();
object[] parametersArray = new object[parameterInfos.Length];
for (int i = 0; i < parameterInfos.Length; i++)
{
ParameterInfo parameterInfo = parameterInfos[i];
object parameter = ExtractParameterFromDictionary(parameterInfo, parameters, MethodInfo);
parametersArray[i] = parameter;
}
ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(MethodInfo);
object actionReturnValue = dispatcher.Execute(controllerContext.Controller, parametersArray);
return actionReturnValue;
}
actionReturnValue
是Action
方法的回傳值.
上面提到Action
使用參數會轉換到一個IDictionary<string, object>
裡面.
key
:參數名稱value
:參數值在
ActionFitlerAttribute.OnActionExcuting
重載方法,參數ActionExecutingContext
物件中有一個屬性public virtual IDictionary<string, object> ActionParameters { get; set; }
他透過IValueProvider
解析完傳入字串轉換成一個存放參數字典.
讓我們了解一下這部分是如何完成.
protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
Dictionary<string, object> parametersDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters();
foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors)
{
parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor);
}
return parametersDict;
}
在呼叫GetParameters
方法返回一個ParameterDescriptor[]
陣列(ParameterDescriptor
存放參數相關資訊),主要呼叫ActionDescriptorHelper.GetParameters
,利用反射取得MethodInfo.GetParameters
在將裡面資訊封裝到ReflectedParameterDescriptor
物件中.
public override ParameterDescriptor[] GetParameters()
{
return ActionDescriptorHelper.GetParameters(this, MethodInfo, ref _parametersCache);
}
public static ParameterDescriptor[] GetParameters(ActionDescriptor actionDescriptor, MethodInfo methodInfo, ref ParameterDescriptor[] parametersCache)
{
ParameterDescriptor[] parameters = LazilyFetchParametersCollection(actionDescriptor, methodInfo, ref parametersCache);
return (ParameterDescriptor[])parameters.Clone();
}
private static ParameterDescriptor[] LazilyFetchParametersCollection(ActionDescriptor actionDescriptor, MethodInfo methodInfo, ref ParameterDescriptor[] parametersCache)
{
return DescriptorUtil.LazilyFetchOrCreateDescriptors(
cacheLocation: ref parametersCache,
initializer: (CreateDescriptorState state) => state.MethodInfo.GetParameters(),
converter: (ParameterInfo parameterInfo, CreateDescriptorState state) => new ReflectedParameterDescriptor(parameterInfo, state.ActionDescriptor),
state: new CreateDescriptorState() { ActionDescriptor = actionDescriptor, MethodInfo = methodInfo });
}
ReflectedParameterDescriptor
包含幾個重要屬性
ParameterType
:參數類型ParameterName
:參數名稱DefaultValue
:參數預設值上面幾個為Action
參數元數據資料.
利用ReflectedParameterDescriptor
之前封裝方法參數資訊對於GetParameterValue
方法執行物件建立.
GetParameterValue
方法中有幾個重要的Field
IModelBinder
使用DefaultModelBinder
來綁定使用參數IValueProvider
依靠ValueProviderFactories
來取使用哪個Provider得並綁訂傳入參數資料.protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
{
Type parameterType = parameterDescriptor.ParameterType;
IModelBinder binder = GetModelBinder(parameterDescriptor);
IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
ModelBindingContext bindingContext = new ModelBindingContext()
{
FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
ModelName = parameterName,
ModelState = controllerContext.Controller.ViewData.ModelState,
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
object result = binder.BindModel(controllerContext, bindingContext);
return result ?? parameterDescriptor.DefaultValue;
}
介紹Action
參數綁定使用點和前置動作(這邊會發現很多Interface
和abstract class
,因為MVC提供許多可以替換點給開發人員擴充)
InvokeAction
方法很重要,他的職責是執行使用者請求的Action
方法,在此方法中有兩個核心物件.
ControllerDescriptor
ActionDescriptor
這兩個物件封裝後續呼叫Action
需要的資訊,特別是ActionDescriptor
裡面有一個Execute
方法(靠他來呼叫Action
方法)
另外也簡單介紹IDictionary<string, object>
這個字典封裝了傳入Action
方法的參數,
最後帶了點Model
綁訂相關使用類別